var Gun = require('./index');
Gun.chain.on = function(tag, arg, eas, as) {
  var gun = this,
    at = gun._,
    tmp,
    act,
    off;
  if (typeof tag === 'string') {
    if (!arg) {
      return at.on(tag);
    }
    act = at.on(tag, arg, eas || at, as);
    if (eas && eas.$) {
      (eas.subs || (eas.subs = [])).push(act);
    }
    return gun;
  }
  var opt = arg;
  opt = true === opt ? { change: true } : opt || {};
  opt.at = at;
  opt.ok = tag;
  //opt.last = {};
  gun.get(ok, opt); // TODO: PERF! Event listener leak!!!?
  return gun;
};

function ok(msg, ev) {
  var opt = this;
  var gun = msg.$,
    at = (gun || {})._ || {},
    data = at.put || msg.put,
    cat = opt.at,
    tmp;
  if (u === data) {
    return;
  }
  if ((tmp = msg.$$)) {
    tmp = msg.$$._;
    if (u === tmp.put) {
      return;
    }
    data = tmp.put;
  }
  if (opt.change) {
    // TODO: BUG? Move above the undef checks?
    data = msg.put;
  }
  // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE
  //if(tmp.put === data && tmp.get === id && !Gun.node.soul(data)){ return }
  //tmp.put = data;
  //tmp.get = id;
  // DEDUPLICATE // TODO: NEEDS WORK! BAD PROTOTYPE
  //at.last = data;
  if (opt.as) {
    opt.ok.call(opt.as, msg, ev);
  } else {
    opt.ok.call(gun, data, msg.get, msg, ev);
  }
}

Gun.chain.val = function(cb, opt) {
  Gun.log.once(
    'onceval',
    'Future Breaking API Change: .val -> .once, apologies unexpected.'
  );
  return this.once(cb, opt);
};
Gun.chain.once = function(cb, opt) {
  var gun = this,
    at = gun._,
    data = at.put;
  if (0 < at.ack && u !== data) {
    (cb || noop).call(gun, data, at.get);
    return gun;
  }
  if (cb) {
    (opt = opt || {}).ok = cb;
    opt.at = at;
    opt.out = { '#': Gun.text.random(9) };
    gun.get(val, { as: opt });
    opt.async = true; //opt.async = at.stun? 1 : true;
  } else {
    Gun.log.once(
      'valonce',
      'Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it.'
    );
    var chain = gun.chain();
    chain._.nix = gun.once(function() {
      chain._.on('in', gun._);
    });
    return chain;
  }
  return gun;
};

function val(msg, eve, to) {
  var opt = this.as,
    cat = opt.at,
    gun = msg.$,
    at = gun._,
    data = at.put || msg.put,
    link,
    tmp;
  if ((tmp = msg.$$)) {
    link = tmp = msg.$$._;
    if (u === tmp.put) {
      return;
    }
    data = tmp.put;
  }
  if ((tmp = eve.wait) && (tmp = tmp[at.id])) {
    clearTimeout(tmp);
  }
  if (
    (!to && (u === data || at.soul || at.link || (link && !(0 < link.ack)))) ||
    (u === data &&
      (tmp = (
        obj_map(at.root.opt.peers, function(v, k, t) {
          t(k);
        }) || []
      ).length) &&
      (link || at).ack <= tmp)
  ) {
    tmp = (eve.wait = {})[at.id] = setTimeout(function() {
      val.call({ as: opt }, msg, eve, tmp || 1);
    }, opt.wait || 99);
    return;
  }
  eve.rid(msg);
  opt.ok.call(gun || opt.$, data, msg.get);
}

Gun.chain.off = function() {
  // make off more aggressive. Warning, it might backfire!
  var gun = this,
    at = gun._,
    tmp;
  var cat = at.back;
  if (!cat) {
    return;
  }
  if ((tmp = cat.next)) {
    if (tmp[at.get]) {
      obj_del(tmp, at.get);
    } else {
    }
  }
  if ((tmp = cat.ask)) {
    obj_del(tmp, at.get);
  }
  if ((tmp = cat.put)) {
    obj_del(tmp, at.get);
  }
  if ((tmp = at.soul)) {
    obj_del(cat.root.graph, tmp);
  }
  if ((tmp = at.map)) {
    obj_map(tmp, function(at) {
      if (at.link) {
        cat.root.$.get(at.link).off();
      }
    });
  }
  if ((tmp = at.next)) {
    obj_map(tmp, function(neat) {
      neat.$.off();
    });
  }
  at.on('off', {});
  return gun;
};
var obj = Gun.obj,
  obj_map = obj.map,
  obj_has = obj.has,
  obj_del = obj.del,
  obj_to = obj.to;
var rel = Gun.val.link;
var empty = {},
  noop = function() {},
  u;
